/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.qire.antsbinder;

import android.os.Build;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import androidx.annotation.Nullable;

import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;

class Platform {
    private static final Platform PLATFORM = findPlatform();

    static Platform get() {
        return PLATFORM;
    }

    private static Platform findPlatform() {
        switch (System.getProperty("java.vm.name")) {
            case "Dalvik":
                return new Android();
            case "RoboVM":
                return new Platform(false);
            default:
                return new Platform(true);
        }
    }

    private final boolean hasJava8Types;
    private final @Nullable Constructor<Lookup> lookupConstructor;

    Platform(boolean hasJava8Types) {
        this.hasJava8Types = hasJava8Types;

        Constructor<Lookup> lookupConstructor = null;
        if (hasJava8Types) {
            try {
                // Because the service interface might not be public, we need to use a MethodHandle lookup
                // that ignores the visibility of the declaringClass.
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    lookupConstructor = Lookup.class.getDeclaredConstructor(Class.class, int.class);
                    lookupConstructor.setAccessible(true);
                }
            } catch (NoClassDefFoundError ignored) {
                // Android API 24 or 25 where Lookup doesn't exist. Calling default methods on non-public
                // interfaces will fail, but there's nothing we can do about it.
            } catch (NoSuchMethodException ignored) {
                // Assume JDK 14+ which contains a fix that allows a regular lookup to succeed.
                // See https://bugs.openjdk.java.net/browse/JDK-8209005.
            }
        }
        this.lookupConstructor = lookupConstructor;
    }

    @IgnoreJRERequirement// Only called on API 24+.
    boolean isDefaultMethod(Method method) {
        return hasJava8Types && method.isDefault();
    }


    @IgnoreJRERequirement// Only called on API 26+.
    @Nullable
    Object invokeDefaultMethod(Method method, Class<?> declaringClass, Object object, Object... args)
            throws Throwable {
        Lookup lookup = lookupConstructor != null ? lookupConstructor.newInstance(declaringClass, -1 /* trusted */)
                        : MethodHandles.lookup();
        return lookup.unreflectSpecial(method, declaringClass).bindTo(object).invokeWithArguments(args);
    }

    static final class Android extends Platform {
        Android() {
            super(Build.VERSION.SDK_INT >= 24);
        }

        @Nullable
        @Override
        Object invokeDefaultMethod(
                Method method, Class<?> declaringClass, Object object, Object... args) throws Throwable {
            if (Build.VERSION.SDK_INT < 26) {
                throw new UnsupportedOperationException(
                        "Calling default methods on API 24 and 25 is not supported");
            }
            return super.invokeDefaultMethod(method, declaringClass, object, args);
        }

    }
}